// <copyright file="LineString.cs" company="Signal Processing Lab, Wuhan University">
// Copyright (c) 2009-2010 All Right Reserved
// </copyright>
// <author>Xinping Deng</author>
// <email>dentoby@gmail.com</email>
// <date>2010-4</date>
// <summary>This is part of Image.NET project.</summary>

namespace SPLWHU.Core.Vectors
{
    using System;
    using System.Collections.Generic;
    using SPLWHU.Core.Exceptions;

    /// <summary>
    /// The summary of LineString.
    /// </summary>
    public class LineString : Geometry, IEnumerable<Point2d>
    {
        /// <summary>
        /// The coordinates in this instance.
        /// </summary>
        private List<Point2d> points;

        /// <summary>
        /// Initializes a new instance of the <see cref="LineString"/> class.
        /// </summary>
        public LineString()
        {
            this.points = new List<Point2d>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LineString"/> class by specifying a list of points.
        /// </summary>
        /// <param name="points">The points of this line string.</param>
        public LineString(List<Point2d> points)
        {
            this.points = points;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LineString"/> class by specifying an array of points.
        /// </summary>
        /// <param name="points">The points of this line string.</param>
        public LineString(params Point2d[] points)
            : this()
        {
            this.points = new List<Point2d>(points.Length);
            for (int i = 0; i < points.Length; i++)
            {
                this.points.Add(points[i]);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LineString"/> class by copying another <see cref="LineString"/>.
        /// </summary>
        /// <param name="line">The line to copy.</param>
        public LineString(LineString line)
            : this()
        {
            this.points = new List<Point2d>(line.PointCount);
            foreach (Point2d p in line)
            {
                this.points.Add(new Point2d(p));
            }
        }

        /// <summary>
        /// Gets the length of this line string.
        /// </summary>
        /// <value>The length of this instance.</value>
        public virtual double Length
        {
            get
            {
                // Sums up the length of all line segments.
                double sum = 0;
                for (int i = 1; i < this.points.Count; i++)
                {
                    sum += this.points[i].Distance(this.points[i - 1]);
                }

                return sum;
            }
        }

        /// <summary>
        /// Gets the start point.
        /// </summary>
        /// <value>The start point.</value>
        public virtual Point2d StartPoint
        {
            get
            {
                try
                {
                    return this.points[0];
                }
                catch(Exception ex)
                {
                    throw new VectorException(ex.Message);
                }
            }
        }

        /// <summary>
        /// Gets the end point.
        /// </summary>
        /// <value>The end point.</value>
        public virtual Point2d EndPoint
        {
            get
            {
                try
                {
                    return this.points[this.points.Count - 1];
                }
                catch (Exception ex)
                {
                    throw new VectorException(ex.Message);
                }
            }
        }

        /// <summary>
        /// Gets the centroid of this instance. Normally, returns the centroid of the bounding box.
        /// </summary>
        /// <value>The centroid of this instance.</value>
        public override Point2d Centroid
        {
            get { return this.GetExtents().Centroid; }
        }

        /// <summary>
        /// Gets the corresponding wkb geometry type (<see cref="GeometryType"/>).
        /// </summary>
        /// <value>An int represents the wkb geometry type.</value>
        public override int TypeFlag
        {
            get { return (int)GeometryType.LineString; }
        }

        /// <summary>
        /// Gets the point count in this geometry.
        /// </summary>
        /// <value>The point count.</value>
        /// <remarks>
        /// A geometry instance except <see cref="Point2d"/> or <see cref="LineString"/> returns zero.
        /// </remarks>
        public override int PointCount
        {
            get { return this.points.Count; }
        }

        /// <summary>
        /// Gets or sets the <see cref="SPLWHU.Core.Vectors.Point2d"/> at the specified position.
        /// </summary>
        /// <param name="index">The index of point</param>
        /// <value>The point at the specified index.</value>
        /// <returns>The point at the specified index</returns>
        public virtual Point2d this[int index]
        {
            get
            {
                try
                {
                    return this.points[index];
                }
                catch
                {
                    throw new VectorException("Can not access the point at {0} in this line string.", index);
                }
            }

            set
            {
                try
                {
                    this.points[index] = value;
                }
                catch
                {
                    throw new VectorException("Can not access the point at {0} in this line string", index);
                }
            }
        }

        /// <summary>
        /// Appends a point to this geometry.
        /// </summary>
        /// <param name="point">The point to add.</param>
        public override void AddPoint(Point2d point)
        {
            this.points.Add(point);
        }

        /// <summary>
        /// Gets the point in this geometry at the specified index.
        /// </summary>
        /// <param name="index">The point index.</param>
        /// <returns>The point at the specified position.</returns>
        public override Point2d GetPoint(int index)
        {
            return this[index];
        }

        /// <summary>
        /// Gets the bounding box of this geometry.
        /// </summary>
        /// <returns>The bounding box of this geometry.</returns>
        public override BoundingBox GetExtents()
        {
            BoundingBox box = this.points[0].GetExtents();

            // Gets the joint of all bounding box.
            for (int i = 1, count = this.points.Count; i < count; i++)
            {
                box = box.Joint(this.points[i].GetExtents());
            }

            return box;
        }

        /// <summary>
        /// Creates a copy of this instance.
        /// </summary>
        /// <returns>The new copy of this instance.</returns>
        public override Geometry Clone()
        {
            return new LineString(this);
        }

        /// <summary>
        /// Appends an array of points to this instance.
        /// </summary>
        /// <param name="ps">The new points.</param>
        public virtual void AddPoints(params Point2d[] ps)
        {
            this.points.AddRange(ps);
        }

        /// <summary>
        /// Inserts a point at the specified index.
        /// </summary>
        /// <param name="index">The index to insert this point.</param>
        /// <param name="p">The point to be insert.</param>
        public virtual void Insert(int index, Point2d p)
        {
            try
            {
                this.points.Insert(index, p);
            }
            catch
            {
                throw new VectorException("Index {0} is out of Range {1}", index, this.points.Count);
            }
        }

        /// <summary>
        /// Removes the specified point.
        /// </summary>
        /// <param name="p">The point to remove.</param>
        public virtual void Remove(Point2d p)
        {
            this.points.Remove(p);
        }

        /// <summary>
        /// Removes the point at the specified position.
        /// </summary>
        /// <param name="index">The position of the point to remove.</param>
        public virtual void RemoveAt(int index)
        {
            try
            {
                this.points.RemoveAt(index);
            }
            catch (Exception ex)
            {
                throw new VectorException(ex.Message);
            }
        }

        /// <summary>
        /// Clears all coordinates in this instance.
        /// </summary>
        public virtual void Clear()
        {
            this.points.Clear();
        }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns>
        /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object obj)
        {
            try
            {
                // Firstly, the object should be a line string.
                LineString line = obj as LineString;
                if (obj == null)
                {
                    return false;
                }

                // Secondly, the count of two line strings must be equal.
                if (this.points.Count != line.points.Count)
                {
                    return false;
                }

                // Thirdly, all points must be equal.
                for (int i = 0, count = this.points.Count; i < count; i++)
                {
                    if (this.points[i] != line.points[i])
                    {
                        return false;
                    }
                }

                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            int hash = 0;
            for (int i = 0, count = this.points.Count; i < count; i++)
            {
                hash = hash ^ this.points[i].GetHashCode();
            }

            return hash;
        }

        #region IEnumerable<Point2d> Members

        /// <summary>
        /// Gets the enumerator of all the points in this instance.
        /// </summary>
        /// <returns>
        /// The enumerator to access the points.
        /// </returns>
        public IEnumerator<Point2d> GetEnumerator()
        {
            return this.points.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// Gets the enumerator of all the points in this instance.
        /// </summary>
        /// <returns>
        /// The enumerator to access the points.
        /// </returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion
    }
}
